{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Project 1 - Balance"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's where we left off with our code:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import itertools\n",
    "import numbers\n",
    "from datetime import timedelta\n",
    "\n",
    "class TimeZone:\n",
    "    def __init__(self, name, offset_hours, offset_minutes):\n",
    "        if name is None or len(str(name).strip()) == 0:\n",
    "            raise ValueError('Timezone name cannot be empty.')\n",
    "            \n",
    "        self._name = str(name).strip()\n",
    "        # technically we should check that offset is a\n",
    "        if not isinstance(offset_hours, numbers.Integral):\n",
    "            raise ValueError('Hour offset must be an integer.')\n",
    "        \n",
    "        if not isinstance(offset_minutes, numbers.Integral):\n",
    "            raise ValueError('Minutes offset must be an integer.')\n",
    "            \n",
    "        if offset_minutes < -59 or offset_minutes > 59:\n",
    "            raise ValueError('Minutes offset must between -59 and 59 (inclusive).')\n",
    "            \n",
    "        # for time delta sign of minutes will be set to sign of hours\n",
    "        offset = timedelta(hours=offset_hours, minutes=offset_minutes)\n",
    "\n",
    "        # offsets are technically bounded between -12:00 and 14:00\n",
    "        # see: https://en.wikipedia.org/wiki/List_of_UTC_time_offsets\n",
    "        if offset < timedelta(hours=-12, minutes=0) or offset > timedelta(hours=14, minutes=0):\n",
    "            raise ValueError('Offset must be between -12:00 and +14:00.')\n",
    "            \n",
    "        self._offset_hours = offset_hours\n",
    "        self._offset_minutes = offset_minutes\n",
    "        self._offset = offset\n",
    "        \n",
    "    @property\n",
    "    def offset(self):\n",
    "        return self._offset\n",
    "    \n",
    "    @property\n",
    "    def name(self):\n",
    "        return self._name\n",
    "    \n",
    "    def __eq__(self, other):\n",
    "        return (isinstance(other, TimeZone) and \n",
    "                self.name == other.name and \n",
    "                self._offset_hours == other._offset_hours and\n",
    "                self._offset_minutes == other._offset_minutes)\n",
    "    def __repr__(self):\n",
    "        return (f\"TimeZone(name='{self.name}', \"\n",
    "                f\"offset_hours={self._offset_hours}, \"\n",
    "                f\"offset_minutes={self._offset_minutes})\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Account:\n",
    "    transaction_counter = itertools.count(100)\n",
    "    \n",
    "    def __init__(self, account_number, first_name, last_name, timezone=None):\n",
    "        # in practice we probably would want to add checks to make sure these values are valid / non-empty\n",
    "        self._account_number = account_number\n",
    "        self.first_name = first_name\n",
    "        self.last_name = last_name\n",
    "        \n",
    "        if timezone is None:\n",
    "            timezone = TimeZone('UTC', 0, 0)\n",
    "        self.timezone = timezone\n",
    "        \n",
    "    @property\n",
    "    def account_number(self):\n",
    "        return self._account_number\n",
    "    \n",
    "    @property \n",
    "    def first_name(self):\n",
    "        return self._first_name\n",
    "    \n",
    "    @first_name.setter\n",
    "    def first_name(self, value):\n",
    "        self.validate_and_set_name('_first_name', value, 'First Name')\n",
    "        \n",
    "    @property\n",
    "    def last_name(self):\n",
    "        return self._last_name\n",
    "    \n",
    "    @last_name.setter\n",
    "    def last_name(self, value):\n",
    "        self.validate_and_set_name('_last_name', value, 'Last Name')\n",
    "        \n",
    "    # also going to create a full_name computed property, for ease of use\n",
    "    @property\n",
    "    def full_name(self):\n",
    "        return f'{self.first_name} {self.last_name}'    \n",
    "    \n",
    "    @property\n",
    "    def timezone(self):\n",
    "        return self._timezone\n",
    "    \n",
    "    @timezone.setter\n",
    "    def timezone(self, value):\n",
    "        if not isinstance(value, TimeZone):\n",
    "            raise ValueError('Time zone must be a valid TimeZone object.')\n",
    "        self._timezone = value\n",
    "            \n",
    "    def validate_and_set_name(self, property_name, value, field_title):\n",
    "        if value is None or len(str(value).strip()) == 0:\n",
    "            raise ValueError(f'{field_title} cannot be empty.')\n",
    "        setattr(self, property_name, value)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next we need to add something to maintain the balance. Again this is going to be a \"read-only\" property, and we'll add it as an optional param to the `__init__` method as well:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Account:\n",
    "    transaction_counter = itertools.count(100)\n",
    "    \n",
    "    def __init__(self, account_number, first_name, last_name, \n",
    "                 timezone=None, initial_balance=0):\n",
    "        # in practice we probably would want to add checks to make sure these values are valid / non-empty\n",
    "        self._account_number = account_number\n",
    "        self.first_name = first_name\n",
    "        self.last_name = last_name\n",
    "        \n",
    "        if timezone is None:\n",
    "            timezone = TimeZone('UTC', 0, 0)\n",
    "        self.timezone = timezone\n",
    "        \n",
    "        self._balance = float(initial_balance)  # force use of floats here, but maybe Decimal would be better\n",
    "        \n",
    "    @property\n",
    "    def account_number(self):\n",
    "        return self._account_number\n",
    "    \n",
    "    @property \n",
    "    def first_name(self):\n",
    "        return self._first_name\n",
    "    \n",
    "    @first_name.setter\n",
    "    def first_name(self, value):\n",
    "        self.validate_and_set_name('_first_name', value, 'First Name')\n",
    "        \n",
    "    @property\n",
    "    def last_name(self):\n",
    "        return self._last_name\n",
    "    \n",
    "    @last_name.setter\n",
    "    def last_name(self, value):\n",
    "        self.validate_and_set_name('_last_name', value, 'Last Name')\n",
    "        \n",
    "    # also going to create a full_name computed property, for ease of use\n",
    "    @property\n",
    "    def full_name(self):\n",
    "        return f'{self.first_name} {self.last_name}'\n",
    "        \n",
    "    @property\n",
    "    def timezone(self):\n",
    "        return self._timezone\n",
    "    \n",
    "    @property\n",
    "    def balance(self):\n",
    "        return self._balance\n",
    "    \n",
    "    @timezone.setter\n",
    "    def timezone(self, value):\n",
    "        if not isinstance(value, TimeZone):\n",
    "            raise ValueError('Time zone must be a valid TimeZone object.')\n",
    "        self._timezone = value\n",
    "            \n",
    "    def validate_and_set_name(self, property_name, value, field_title):\n",
    "        if value is None or len(str(value).strip()) == 0:\n",
    "            raise ValueError(f'{field_title} cannot be empty.')\n",
    "        setattr(self, property_name, value)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "100.0\n"
     ]
    }
   ],
   "source": [
    "a = Account('1234', 'John', 'Cleese', initial_balance=100)\n",
    "print(a.balance)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "can't set attribute\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    a.balance = 200\n",
    "except AttributeError as ex:\n",
    "    print(ex)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
